<?php
/**
 * Note: 闪验
 */

namespace App\Services;

use App\Exceptions\ApiException;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;

class ShanYanService
{
    protected const API_MOBILE_QUERY = 'https://api.253.com/open/flashsdk/mobile-query';
    protected const API_TEL_ADDRESS = 'https://api.253.com/open/unn/teladress';
    protected const API_BALANCE_SMS = 'http://smssh1.253.com/msg/balance/json';
    protected const API_BALANCE_FLASH_SDK = 'http://api.253.com/open/get-balance';

    protected $config;

    public function __construct()
    {

    }

    /**
     * @param array $config
     * @return $this
     */
    public function setConfig(array $config): ShanYanService
    {
        $this->config = $config;
        return $this;
    }

    /**
     * 发送post请求
     * @param $url
     * @param $data
     * @return Response
     * @throws \Exception
     */
    protected function requestPost($url, $data): Response
    {
        // 发生错误时 100ms 后重试 1 次, 每次超时3s (设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw), 这里放入try
        try {
            info(__METHOD__, [$url, $data]);
            $response = Http::retry(2, 100)->timeout(3)->asForm()->post($url, $data);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception("闪验正在维护中，请联系客服");
        }
        return $response;
    }

    /**
     * 发送post请求
     * @param $url
     * @param $data
     * @return Response
     * @throws \Exception
     */
    protected function requestPostJson($url, $data): Response
    {
        // 发生错误时 100ms 后重试 1 次, 每次超时3s (设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw), 这里放入try
        try {
            info(__METHOD__, [$url, $data]);
            $response = Http::retry(2, 100)->timeout(3)->asJson()->post($url, $data);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception("闪验正在维护中，请联系客服");
        }
        return $response;
    }

    /**
     * 生成签名
     * @param array $params
     * @return string
     */
    protected function makeSign(array $params): string
    {
        $signString = '';
        ksort($params);
        foreach ($params as $k => $v) {
            $signString .= $k . $v;
        }
        return bin2hex(hash_hmac('sha256', $signString, $this->config['app_key'], true));
    }

    /**
     * 根据 token 查询手机号
     * @param string $token
     * @return string
     * @throws \Exception
     */
    public function mobileQuery(string $token): string
    {
        $params = [
            'appId' => $this->config['app_id'],
            'token' => $token,
        ];
        $params['sign'] = $this->makeSign($params);
        $response = $this->requestPost(self::API_MOBILE_QUERY, $params);
        if ($response['code'] !== '200000') throw new \Exception("手机号查询失败: " . ($response['message'] ?? '未知错误'));

        $mobile = $response['data']['mobileName']; // 手机号
        $key = md5($this->config['app_key']);
        $mobile = openssl_decrypt(hex2bin($mobile), 'AES-128-CBC', substr($key, 0, 16), OPENSSL_RAW_DATA, substr($key, 16));
        if (!$mobile) {
            info(__METHOD__, ['解密失败, 请检查', $params, $response]);
            throw new \Exception('手机号查询失败, 请重试');
        }
        info(__METHOD__, [$mobile]);
        // todo 收费记录
        return $mobile;
    }

    /**
     * 手机归属地查询
     * @param string $mobile
     * @return array {"orderNo":"138077170362908","handleTime":"2022-10-08 15:58:22","province":"广西","city":"南宁","provinceCode":"0771","cityCode":"450100","isp":"移动","mobile":"13807717036","postCode":"530000"}
     * @throws ApiException
     */
    public function telAddress(string $mobile): array
    {
        $config = config('services.shanyanTelAddress');
        $params = [
            'appId' => $config['app_id'],
            'appKey' => $config['app_key'],
            'mobile' => $mobile,
            'orderNo' => $mobile.mt_rand(1000, 9999),
        ];
        $response = $this->requestPost(self::API_TEL_ADDRESS, $params);
        // {"tradeNo":"1028335556650360832","chargeStatus":1,"message":"成功","data":{"orderNo":"138077170362908","handleTime":"2022-10-08 15:58:22","province":"广西","city":"南宁","provinceCode":"0771","cityCode":"450100","isp":"移动","mobile":"13807717036","postCode":"530000"},"code":"200000"}
        if ($response['code'] !== '200000') throw new ApiException("号码查询失败: " . ($response['message'] ?? '未知错误'));

        return $response['data'];
    }

    /**
     * 手机归属地检查
     * @param string $mobile
     * @return bool
     * @throws ApiException
     */
    public function isTelAddressWarning(string $mobile): bool
    {
        $res = $this->telAddress($mobile);

        info(__METHOD__, ["{$mobile} 区域代码: {$res['cityCode']}, 是否拦截", substr($res['cityCode'], 0, 2) === '45']);
        return substr($res['cityCode'], 0, 2) === '45';
    }

    /**
     * 查询短信余额(条数)
     * @return mixed
     * @throws ApiException
     */
    public function getBalanceSms()
    {
        $response = $this->requestPostJson(self::API_BALANCE_SMS, [
            'account' => config('ibrand.sms.easy_sms.gateways.chuanglan.account'),
            'password' => config('ibrand.sms.easy_sms.gateways.chuanglan.password'),
        ]);
        if ($response['code'] !== 0) throw new ApiException('验证码余额查询失败');
        return $response['balance'];
    }

    /**
     * 查询闪验SDK余额(元)
     * @return mixed
     * @throws ApiException
     */
    public function getBalanceFlashSdk()
    {
        $response = $this->requestPost(self::API_BALANCE_FLASH_SDK, [
            'appId' => $this->config['app_id'],
            'appKey' => $this->config['app_key'],
        ]);
        if ($response['code'] !== '200000') throw new ApiException('闪验SDK余额查询失败');
        return $response['data'];
    }
}
